#if !defined(TASK_H_)
#define TASK_H_
#include <condition_variable>
#include <functional>
#include <future>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>

namespace autodrive {
namespace perception {
/// @brief async Task
/// @tparam _ReturnType return type
template <typename _ReturnType>
class Task final {
 public:
  Task() : is_exit_{false}, is_task_init_{false}, status_{TaskStatus::NONE} {};
  Task(Task &&) = delete;
  Task(const Task &) = delete;
  Task &operator=(Task &&) = delete;
  Task &operator=(const Task &) = delete;
  ~Task() {
    std::lock_guard<std::mutex> lock(sync_);
    try {
      is_exit_ = true;
      {
        std::unique_lock<std::mutex> l(mutex_);
        cv_.notify_all();
      }

      if (th_task_.joinable()) {
        th_task_.join();
      }
      status_ = TaskStatus::EXIT;
    } catch (const std::exception &e) {
      std::cerr << "[" << to_red("Error") << "]" << " task exit error, "
                << e.what() << '\n';
    } catch (...) {
      std::cerr << "[" << to_red("Error") << "]"
                << " task exit unexpected error!!!" << '\n';
    }
  };

  /// @brief taks status
  enum class TaskStatus : std::int8_t {
    /// @brief none
    NONE = 0,
    /// @brief waiting
    WAITING = 1,
    /// @brief running
    RUNNING = 2,
    /// @brief task done
    DONE = 3,
    /// @brief exit
    EXIT = 4
  };

 private:
  template <typename _Res, typename _Fn>
  struct TaskFunInvoker {
    void operator()(std::promise<_Res> &promise, _Fn &&__fn) {
      promise.set_value(std::forward<_Res>(__fn()));
    }
  };

  template <typename _Fn>
  struct TaskFunInvoker<void, _Fn> {
    void operator()(std::promise<void> &promise, _Fn &&__fn) {
      __fn();
      promise.set_value();
    }
  };

 public:
  /// @brief run aysn
  /// @tparam _Fn function type
  /// @tparam ..._Args args type
  /// @param __fn function
  /// @param ...__args args
  /// @return future
  template <typename _Fn>
  std::future<_ReturnType> Run(_Fn &&__fn) noexcept(false) {
    using _Res = std::invoke_result_t<_Fn>;
    static_assert(std::is_same_v<_Res, _ReturnType>,
                  "Task return type is not same with function return type");
    TaskFunInvoker<_Res, _Fn> funInvoker;
    auto taskFunc{[&]() {
      while (!is_exit_) {
        std::unique_lock<std::mutex> l(mutex_);
        status_ = TaskStatus::WAITING;
        cv_.wait(l);
        if (is_exit_) {
          return;
        }
        status_ = TaskStatus::RUNNING;

        try {
          funInvoker(promise_, __fn);
        } catch (...) {
          try {
            promise_.set_exception(std::current_exception());
          } catch (...) {
            std::cerr << "[Error] promise set excetion error" << std::endl;
          }
        }
        status_ = TaskStatus::DONE;
      }
    }};

    std::lock_guard<std::mutex> lock(sync_);
    if (!is_task_init_) {
      th_task_ = std::thread(taskFunc);
      is_task_init_ = true;
      while (true) {
        if (Status() == TaskStatus::WAITING) {
          break;
        } else {
          sleep(1);
        }
      }
    }

    if (Status() != TaskStatus::WAITING) {
      throw std::runtime_error("task run error, task is not waiting");
    }

    std::promise<_ReturnType> p;
    promise_ = std::forward<std::promise<_ReturnType>>(p);

    std::unique_lock<std::mutex> l(mutex_);
    cv_.notify_one();
    return promise_.get_future();
  };

  /// @brief get task status
  /// @return task status
  TaskStatus Status() noexcept { return status_; }

 protected:
  /// @brief console to red
  /// @param str message
  /// @return console red message
  inline std::string to_red(std::string const &str) noexcept {
    return "\033[1;5;31m" + str + "\033[0m";
  }

  /// @brief sleep for millisecond
  /// @param milliseconds
  inline void sleep(std::uint32_t milliseconds) noexcept {
    std::chrono::milliseconds dura{milliseconds};
    std::this_thread::sleep_for(dura);
  }

 private:
  /// @brief is exit flag
  bool is_exit_;
  /// @brief thread task
  std::thread th_task_;
  /// @brief is task init flag
  bool is_task_init_;
  /// @brief mutex
  std::mutex mutex_;
  /// @brief mutex init
  std::mutex sync_;
  /// @brief condition variable
  std::condition_variable cv_;
  /// @brief task status
  TaskStatus status_;
  /// @brief promise to save result
  std::promise<_ReturnType> promise_;
};
}  // namespace perception
}  // namespace autodrive

#endif  // TASK_H_
